﻿using Sprout.Core.Attributes;
using Sprout.Core.Enums;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Reflection;

namespace Sprout.Core.Utils
{
    public class ExcelHelper
    {
        #region 读取成 datatable
        /// <summary>
        /// 将excel文件内容读取到DataTable数据表中
        /// </summary>
        /// <param name="path">文件完整路径名</param>
        /// <param name="sheetName">指定读取excel工作薄sheet的名称</param>
        /// <param name="isFirstRowColumn">第一行是否是DataTable的列名：true=是，false=否</param>
        /// <returns>DataTable数据表</returns>
        public static DataTable ToDataTable(string path, string sheetName = null, bool isFirstRowColumn = true)
        {
            if (!File.Exists(path))
            {
                return null;
            }
            //根据指定路径读取文件
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

            return ToDataTable(fs, sheetName, isFirstRowColumn);
        }

        /// <summary>
        /// 将excel文件流读取到DataTable数据表中
        /// </summary>
        /// <param name="stream">文件流</param>
        /// <param name="sheetName">指定读取excel工作薄sheet的名称</param>
        /// <param name="isFirstRowColumn">第一行是否是DataTable的列名：true=是，false=否</param>
        /// <returns>DataTable数据表</returns>
        public static DataTable ToDataTable(Stream stream, string sheetName = null, bool isFirstRowColumn = true)
        {
            //定义要返回的datatable对象
            DataTable data = new DataTable();
            //excel工作表
            ISheet sheet = null;
            //数据开始行(排除标题行)
            int startRow = 0;
            try
            {
                //根据文件流创建excel数据结构,NPOI的工厂类WorkbookFactory会自动识别excel版本，创建出不同的excel数据结构
                IWorkbook workbook = WorkbookFactory.Create(stream);
                //如果有指定工作表名称
                if (!string.IsNullOrEmpty(sheetName))
                {
                    sheet = workbook.GetSheet(sheetName);
                    //如果没有找到指定的sheetName对应的sheet，则尝试获取第一个sheet
                    if (sheet == null)
                    {
                        sheet = workbook.GetSheetAt(0);
                    }
                }
                else
                {
                    //如果没有指定的sheetName，则尝试获取第一个sheet
                    sheet = workbook.GetSheetAt(0);
                }
                if (sheet != null)
                {
                    IRow firstRow = sheet.GetRow(0);
                    //一行最后一个cell的编号 即总的列数
                    int cellCount = firstRow.LastCellNum;
                    //如果第一行是标题列名
                    if (isFirstRowColumn)
                    {
                        for (int i = firstRow.FirstCellNum; i < cellCount; ++i)
                        {
                            ICell cell = firstRow.GetCell(i);
                            if (cell != null)
                            {
                                string cellValue = cell.StringCellValue;
                                if (cellValue != null)
                                {
                                    DataColumn column = new DataColumn(cellValue);
                                    data.Columns.Add(column);
                                }
                            }
                        }
                        startRow = sheet.FirstRowNum + 1;
                    }
                    else
                    {
                        startRow = sheet.FirstRowNum;
                    }
                    //最后一列的标号
                    int rowCount = sheet.LastRowNum;
                    for (int i = startRow; i <= rowCount; ++i)
                    {
                        IRow row = sheet.GetRow(i);
                        if (row == null || row.FirstCellNum < 0) continue; //没有数据的行默认是null　　　　　　　

                        DataRow dataRow = data.NewRow();
                        for (int j = row.FirstCellNum; j < cellCount; ++j)
                        {
                            //同理，没有数据的单元格都默认是null
                            ICell cell = row.GetCell(j);
                            if (cell != null)
                            {
                                dataRow[j] = GetCellValue(cell);
                            }
                        }
                        data.Rows.Add(dataRow);
                    }
                }
                return data;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        #endregion

        #region 读取excel
        /// <summary>
        /// 返回实体数据列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">文件流</param>
        /// <param name="sheetName">表单名称</param>
        /// <param name="headerAndPropertyNameDic">header名，属性名</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <param name="dataIndex">数据行起始索引</param>
        /// <returns>实体数据列表</returns>
        public static IList<T> ToEntityList<T>(Stream stream, string sheetName, Dictionary<string, string> headerAndPropertyNameDic, int headerIndex = 0, int dataIndex = 1)
        where T : class, new()
        {
            IWorkbook workbook = WorkbookFactory.Create(stream);
            ISheet sheet = GetSheet(workbook, sheetName);

            var propertyIndexDic = GetPropertyIndexDic<T>(sheet, headerAndPropertyNameDic, headerIndex);

            return ToEntityList<T>(sheet, propertyIndexDic, dataIndex);
        }
        /// <summary>
        /// 返回实体数据列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path">文件路径</param>
        /// <param name="sheetName">表单名称</param>
        /// <param name="headerAndPropertyNameDic">header名，属性名</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <param name="dataIndex">数据行起始索引</param>
        /// <returns>实体数据列表</returns>
        public static IList<T> ToEntityList<T>(string path, string sheetName, Dictionary<string, string> headerAndPropertyNameDic, int headerIndex = 0, int dataIndex = 1)
            where T : class, new()
        {
            if (!File.Exists(path))
            {
                return null;
            }
            //根据指定路径读取文件
            FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
            return ToEntityList<T>(stream, sheetName, headerAndPropertyNameDic, headerIndex, dataIndex);
        }

        /// <summary>
        /// 返回实体数据列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream">文件流</param>
        /// <param name="sheetName">表单名称</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <param name="dataIndex">数据行起始索引</param>
        /// <returns>实体数据列表</returns>
        public static IList<T> ToEntityList<T>(Stream stream, string sheetName, int headerIndex = 0, int dataIndex = 1)
        where T : class, new()
        {
            //根据文件流创建excel数据结构,NPOI的工厂类WorkbookFactory会自动识别excel版本，创建出不同的excel数据结构
            IWorkbook workbook = WorkbookFactory.Create(stream);
            ISheet sheet = GetSheet(workbook, sheetName);
            var propertyIndexDic = GetPropertyIndexDic<T>(sheet, headerIndex);
            return ToEntityList<T>(sheet, propertyIndexDic, dataIndex);
        }
        /// <summary>
        /// 返回实体数据列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path">文件路径</param>
        /// <param name="sheetName">表单名称</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <param name="dataIndex">数据行起始索引</param>
        /// <returns>实体数据列表</returns>
        public static IList<T> ToEntityList<T>(string path, string sheetName, int headerIndex = 0, int dataIndex = 1)
            where T : class, new()
        {
            if (!File.Exists(path))
            {
                return null;
            }
            //根据指定路径读取文件
            FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
            return ToEntityList<T>(stream, sheetName, headerIndex, dataIndex);
        }

        /// <summary>
        /// 获取Sheet
        /// </summary>
        /// <param name="workbook">IWorkbook</param>
        /// <param name="sheetName">表单名称</param>
        /// <returns></returns>
        private static ISheet GetSheet(IWorkbook workbook, string sheetName)
        {
            ISheet sheet = null;
            //如果有指定工作表名称
            if (!string.IsNullOrEmpty(sheetName))
            {
                sheet = workbook.GetSheet(sheetName);
                //如果没有找到指定的sheetName对应的sheet，则尝试获取第一个sheet
                if (sheet == null)
                {
                    sheet = workbook.GetSheetAt(0);
                }
            }
            else
            {
                //如果没有指定的sheetName，则尝试获取第一个sheet
                sheet = workbook.GetSheetAt(0);
            }
            return sheet;
        }
        /// <summary>
        /// 返回实体数据列表
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sheet">ISheet</param>
        /// <param name="propertyIndexDic">属性，索引字典</param>
        /// <param name="dataIndex">数据行起始索引</param>
        /// <returns>数据列表</returns>
        private static IList<T> ToEntityList<T>(ISheet sheet, Dictionary<PropertyInfo, int> propertyIndexDic, int dataIndex = 1)
            where T : class, new()
        {
            List<T> list = new List<T>();
            IRow row = null;
            ICell cell = null;
            for (int i = dataIndex; i <= sheet.LastRowNum; i++)
            {
                row = sheet.GetRow(i);
                if (row == null || row.FirstCellNum < 0) continue;
                T entity = new T();

                foreach (var keyvalue in propertyIndexDic)
                {
                    cell = row.GetCell(keyvalue.Value);
                    if (cell != null)
                    {
                        var value = GetCellValue(cell);

                        ReflectionHelper.SetPropertyValue(entity, value, keyvalue.Key);
                    }
                }

                list.Add(entity);
            }

            return list;
        }

        /// <summary>
        /// 获取cell文本
        /// </summary>
        /// <param name="cell"></param>
        /// <returns></returns>
        private static string GetCellValue(ICell cell)
        {
            string value = string.Empty;

            if (cell.CellType == CellType.Numeric || cell.CellType == CellType.Formula)
            {
                if (DateUtil.IsCellDateFormatted(cell))
                {
                    value = cell.DateCellValue.ToString();
                }
                else
                {
                    value = cell.ToString();
                }
            }
            else
            {
                value = cell.ToString();
            }

            return value == string.Empty ? null : value;
        }
        #endregion

        #region 获取属性和excel索引
        /// <summary>
        /// 获取属性和excel列索引的关系
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sheet">ISheet</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <returns>属性名，索引</returns>
        private static Dictionary<PropertyInfo, int> GetPropertyIndexDic<T>(ISheet sheet, int headerIndex)
        {
            var headerIndexDic = GetExcelHeaderIndexDic(sheet, headerIndex);

            Dictionary<PropertyInfo, int> dic = new Dictionary<PropertyInfo, int>();
            Type type = typeof(T);
            foreach (var property in type.GetProperties())
            {
                int index = -1;
                ExcelReadAttribute attribute = property.GetCustomAttribute<ExcelReadAttribute>();
                if (attribute != null)
                {
                    if (attribute.Index == -1)
                    {
                        if (!string.IsNullOrEmpty(attribute.ColumnName))
                        {
                            if (headerIndexDic.ContainsKey(attribute.ColumnName))
                            {
                                index = headerIndexDic[attribute.ColumnName];
                            }
                        }
                    }
                    else
                    {
                        index = attribute.Index;
                    }
                }
                if (index > -1)
                {
                    dic.Add(property, index);
                }
            }

            return dic;
        }
        /// <summary>
        /// 获取属性和excel列索引的关系
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sheet">ISheet</param>
        /// <param name="headerAndPropertyNameDic">header头，属性名</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <returns>属性名，索引</returns>
        private static Dictionary<PropertyInfo, int> GetPropertyIndexDic<T>(ISheet sheet, Dictionary<string, string> headerAndPropertyNameDic, int headerIndex)
        {
            var headerIndexDic = GetExcelHeaderIndexDic(sheet, headerIndex);

            Dictionary<PropertyInfo, int> propertyIndexDic = new Dictionary<PropertyInfo, int>();
            Type type = typeof(T);
            var propertyNameDic = ReflectionHelper.GetPropertyNameDic(type);
            foreach (var keyvalue in headerAndPropertyNameDic)
            {
                if (headerIndexDic.ContainsKey(keyvalue.Key))
                {
                    if (propertyNameDic.ContainsKey(keyvalue.Value))
                    {
                        propertyIndexDic.Add(propertyNameDic[keyvalue.Value], headerIndexDic[keyvalue.Key]);
                    }
                }
            }

            return propertyIndexDic;
        }

        #endregion

        #region 获取excel列头和索引
        /// <summary>
        /// 获取excel列头和索引
        /// </summary>
        /// <param name="sheet">ISheet</param>
        /// <param name="headerIndex">表头起始索引</param>
        /// <returns>表头，索引</returns>
        public static Dictionary<string, int> GetExcelHeaderIndexDic(ISheet sheet, int headerIndex = 0)
        {
            Dictionary<string, int> dic = new Dictionary<string, int>();
            if (headerIndex < 0)
            {
                return dic;
            }

            IRow row = sheet.GetRow(headerIndex);

            for (int i = row.FirstCellNum; i < row.LastCellNum; ++i)
            {
                ICell cell = row.GetCell(i);
                if (cell != null)
                {
                    string cellValue = GetCellValue(cell);
                    if (cellValue != null)
                    {
                        dic.Add(cellValue, i);
                    }
                }
            }
            return dic;
        }
        #endregion

        #region 导出EXCEL
        /// <summary>
        /// 写入excel，生成内存流
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">实体集合</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns>excel文件字节</returns>
        public static byte[] ToExcel<T>(IEnumerable<T> list, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            List<ExcelParamsDto> excelParams = GetExcelParamsDto<T>();
            return ToExcel(list, excelParams, dataIndex, excelVersion);
        }
        /// <summary>
        /// 写入excel，生成内存流
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">实体集合</param>
        /// <param name="propertyNameAndExcelDic">属性名，excel参数</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns>excel文件字节</returns>
        public static byte[] ToExcel<T>(IEnumerable<T> list, Dictionary<string, ExcelParamsDto> propertyNameAndExcelDic, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            List<ExcelParamsDto> excelParams = new List<ExcelParamsDto>();
            var type = typeof(T);
            var propertyNameDic = ReflectionHelper.GetPropertyNameDic(type);
            foreach (var keyvalue in propertyNameAndExcelDic)
            {
                if (propertyNameDic.ContainsKey(keyvalue.Key))
                {
                    var excelParam = keyvalue.Value;
                    excelParam.Property = propertyNameDic[keyvalue.Key];
                    excelParams.Add(excelParam);
                }
            }
            return ToExcel(list, excelParams, dataIndex, excelVersion);
        }

        /// <summary>
        /// 写入excel，生成内存流
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="list">实体集合</param>
        /// <param name="excelParams">excel列参数</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns>excel文件字节</returns>
        public static byte[] ToExcel<T>(IEnumerable<T> list, IEnumerable<ExcelParamsDto> excelParams, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            IWorkbook workbook;
            switch (excelVersion)
            {
                case ExcelVersion.xls:
                    workbook = new HSSFWorkbook();
                    break;
                case ExcelVersion.xlsx:
                    workbook = new XSSFWorkbook();
                    break;
                default:
                    throw new Exception("暂不支持的版本");
            }

            var sheet = workbook.CreateSheet();
            var headerRow = sheet.CreateRow(0);
            Dictionary<int, ICellStyle> cellStyleDic = null;

            excelParams = excelParams.OrderBy(n => n.Sort).ToList();

            int i = 0;
            foreach (var excelParam in excelParams)
            {
                sheet.SetColumnWidth(i, excelParam.ColumnWidth * 256);
                headerRow.CreateCell(i).SetCellValue(excelParam.ColumnName);
                i++;
            }

            return ToExcel(sheet, list, excelParams, dataIndex, cellStyleDic);

        }
        /// <summary>
        /// 写入excel，生成内存流
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sheet">ISheet</param>
        /// <param name="list">实体集合</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <returns>excel文件字节</returns>
        public static byte[] ToExcel<T>(ISheet sheet, IEnumerable<T> list, int dataIndex = 1)
        {
            List<ExcelParamsDto> excelParams = GetExcelParamsDto<T>();
            return ToExcel(sheet, list, excelParams, dataIndex, null);
        }

        /// <summary>
        /// 写入excel，生成内存流
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sheet">ISheet</param>
        /// <param name="list">实体集合</param>
        /// <param name="excelParams">excel列参数</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="cellStyleDic">单元格样式（索引，样式）</param>
        /// <returns>excel文件字节</returns>
        public static byte[] ToExcel<T>(ISheet sheet, IEnumerable<T> list, IEnumerable<ExcelParamsDto> excelParams, int dataIndex = 1, Dictionary<int, ICellStyle> cellStyleDic = null)
        {
            var rowNumber = dataIndex;

            object value = null;
            if (list != null && list.Count() > 0)
            {
                excelParams = excelParams.OrderBy(n => n.Sort).ToList();

                Type type = typeof(T);
                foreach (var item in list)
                {
                    var row = sheet.CreateRow(rowNumber++);

                    int i = 0;
                    foreach (var excelParam in excelParams)
                    {
                        value = ReflectionHelper.GetPropertyValue(item, excelParam.Property);

                        if (value == null)
                        {
                            row.CreateCell(i).SetCellValue("");
                        }
                        else
                        {
                            row.CreateCell(i).SetCellValue(value.ToString());
                        }

                        if (cellStyleDic != null && cellStyleDic.ContainsKey(i))
                        {
                            row.GetCell(i).CellStyle = cellStyleDic[i];
                        }

                        i++;
                    }
                }
            }
            var stream = new MemoryStream();
            sheet.Workbook.Write(stream);
            var buffer = stream.ToArray();
            return buffer;
        }

        /// <summary>
        /// 获取Excel参数
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        private static List<ExcelParamsDto> GetExcelParamsDto<T>()
        {
            Type type = typeof(T);
            List<ExcelParamsDto> excelParams = new List<ExcelParamsDto>();
            foreach (var property in type.GetProperties())
            {
                var attribute = property.GetCustomAttribute<ExcelWriteAttribute>();
                if (attribute != null)
                {
                    excelParams.Add(new ExcelParamsDto()
                    {
                        ColumnName = attribute.ColumnName,
                        ColumnWidth = attribute.ColumnWidth,
                        Sort = attribute.Sort,
                        Property = property
                    });
                }
            }

            return excelParams;
        }
        #endregion

        #region 保存excel
        /// <summary>
        /// 保存excel文件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="directoryPath">文件夹路径</param>
        /// <param name="fileName">文件名</param>
        /// <param name="list">实体集合</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns></returns>
        public static bool SaveExcel<T>(string directoryPath, string fileName, IEnumerable<T> list, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            var buffer = ToExcel(list, dataIndex, excelVersion);

            return FileHelper.SaveFile(directoryPath, fileName, buffer);
        }
        /// <summary>
        /// 保存excel文件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="directoryPath">文件夹路径</param>
        /// <param name="fileName">文件名</param>
        /// <param name="list">实体集合</param>
        /// <param name="propertyNameAndExcelDic">属性名，excel参数</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns></returns>
        public static bool SaveExcel<T>(string directoryPath, string fileName, IEnumerable<T> list, Dictionary<string, ExcelParamsDto> propertyNameAndExcelDic, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            var buffer = ToExcel(list, propertyNameAndExcelDic, dataIndex, excelVersion);

            return FileHelper.SaveFile(directoryPath, fileName, buffer);
        }
        /// <summary>
        /// 保存excel文件
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="directoryPath">文件夹路径</param>
        /// <param name="fileName">文件名</param>
        /// <param name="list">实体集合</param>
        /// <param name="excelParams">excel列参数</param>
        /// <param name="dataIndex">数据开始行数</param>
        /// <param name="excelVersion">excel版本</param>
        /// <returns></returns>
        public static bool SaveExcel<T>(string directoryPath, string fileName, IEnumerable<T> list, IEnumerable<ExcelParamsDto> excelParams, int dataIndex = 1, ExcelVersion excelVersion = ExcelVersion.xlsx)
        {
            var buffer = ToExcel(list, excelParams, dataIndex, excelVersion);

            return FileHelper.SaveFile(directoryPath, fileName, buffer);
        }

        #endregion

    }
}
